package com.eraasoftware.npa.io;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;

import com.esotericsoftware.minlog.Log;

public class ChannelBuffer {
	public static final byte[] DEFAULT_END_OF_STREAM = { 13, 10 };
	private static byte[] END_OF_STREAM = DEFAULT_END_OF_STREAM;
	ByteBuffer buffer;
	ByteChannel channel;
	byte[] endOfStream;
	boolean readable = true;
	boolean writable = true;

	/**
	 * 
	 * @param channel
	 * @param size
	 */

	public ChannelBuffer(ByteChannel channel, int size) {
		buffer = ByteBuffer.allocate(size);
		this.channel = channel;
		endOfStream = new byte[END_OF_STREAM.length];
	}

	/**
	 * Write bytes array to internal buffer. <br>
	 * Automatic write in the channel when the buffer is full.
	 * 
	 * @param b
	 *            : bytes array to write
	 * @throws IOException
	 */
	public void write(byte[] b) throws IOException {
		if (b.length + buffer.position() <= buffer.capacity()) {
			buffer.put(b);
		} else {
			flush();
			buffer.put(b);

		}

	}

	/**
	 * Transfer content buffer into the internal buffer. <br>
	 * Automatic write in the channel when the internal buffer is full
	 * 
	 * @param bb
	 * @throws IOException
	 */
	public void write(ByteBuffer bb) throws IOException {
		if (bb.limit() + buffer.position() <= buffer.capacity()) {
			buffer.put(bb);
		} else {
			flush();
			buffer.put(bb);
		}
	}

	public void flush() throws IOException {
		buffer.flip();
		channel.write(buffer);
		if (Log.DEBUG) {
			buffer.flip();
			System.out.print("flush {");
			for (int n = 0; n < buffer.limit(); n++) {
				System.out.print(buffer.get(n));
				System.out.print(',');
			}
			System.out.println("}");
		}
		buffer.clear();
	}

	public int capacity() {
		return buffer.capacity();
	}

	public ByteBuffer buffer() {
		buffer.flip();
		return buffer;
	}

	public int read() throws IOException {
		int read = channel.read(buffer);
		if (Log.DEBUG) {
			System.out.print("read " + read + " {");
			for (int n = 0; n < read; n++) {
				System.out.print(buffer.get(n) + ",");

			}
			System.out.println("}");

		}

		if (read < 0) {
			readable = false;
			writable = false;
		} else if (isEndOfStream(buffer)) {
			readable = false;
			writable = true;
		}
		return read;
	}

	public void send() throws IOException {
		write(END_OF_STREAM);
		flush();
		Log.info("sent");
		readable = true;
		writable = false;

	}

	private boolean isEndOfStream(ByteBuffer bb) {
		int lim = bb.position();
		if (lim >= endOfStream.length) {
			for (int n = 0; n < endOfStream.length; n++) {
				endOfStream[n] = bb.get(lim - endOfStream.length + n);
			}
		} else {
			for (int n = 0; n < lim; n++) {
				endOfStream[n] = endOfStream[n + 1];
			}
			for (int n = lim; n < endOfStream.length; n++) {
				endOfStream[n] = bb.get(lim - endOfStream.length + n);
			}
		}
		for (int n = 0; n < endOfStream.length; n++) {
			if (endOfStream[n] != END_OF_STREAM[n]) {
				return false;
			}
		}
		return true;
	}

	public ByteChannel channel() {
		return channel;
	}

	public boolean readable() {
		return readable;
	}

	public boolean writable() {
		return writable;
	}

	public boolean remaining() {
		if (readable) {
			return true;
		} else if (writable) {
			return (buffer.position() + END_OF_STREAM.length) < buffer.limit();
		}

		return false;
	}

	public void clear() {
		buffer.clear();

	}

}
